using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;

namespace Camalon.ColorPicker
{
	/// <summary>
	/// Summary description for ColorWheel.
	/// </summary>
	internal class ColorWheelHelper:IDisposable 
	{

		// These resources should be disposed
		// of when you're done with them.
		private Region		_colorRegion;
		private	Bitmap		_colorImage;

		// The code needs to convert back and forth between 
		// degrees and radians. There are 2*PI radians in a 
		// full circle, and 360 degrees. This constant allows
		// you to convert back and forth.
		const double		DEGREES_PER_RADIAN = 180.0 / Math.PI;

		// COLOR_COUNT represents the number of distinct colors
		// used to create the circular gradient. Its value 
		// is somewhat arbitrary -- change this to 6, for 
		// example, to see what happens. 1536 (6 * 256) seems 
		// a good compromise -- it's enough to get a full 
		// range of colors, but it doesn't overwhelm the processor
		// attempting to generate the image. The color wheel
		// contains 6 sections, and each section displays 
		// 256 colors. Seems like a reasonable compromise.
		private const int	COLOR_COUNT = 6 * 256;
		private PointF		_centerPoint;
		private float		_radius;
		private RectangleF	_colorRectangle;

		private HSB		_hsb		= new HSB(0,0,0);
		private PointF	_colorPoint = Point.Empty;

		public ColorWheelHelper(  RectangleF colorRectangle ):this( colorRectangle, Color.White){}
		
		public ColorWheelHelper(  RectangleF colorRectangle , Color backColor )
		{

			// Caller must provide locations for color wheel
			// (_colorRectangle), brightness "strip" (brightnessRectangle)
			// and location to display selected color (selectedColorRectangle).

			using (GraphicsPath path = new GraphicsPath())
			{
				// Store away locations for later use. 
				_colorRectangle = colorRectangle;

				// Calculate the center of the circle.
				// Start with the location, then offset
				// the point by the _radius.
				// Use the smaller of the width and height of
				// the _colorRectangle value.
				_radius = Convert.ToSingle( Math.Min(colorRectangle.Width, colorRectangle.Height) / 2.0);
				_centerPoint = colorRectangle.Location;
				//_centerPoint.Offset((int)_radius, (int)_radius);
				_centerPoint.X+=_radius;
				_centerPoint.Y+=_radius;
				// Create a region corresponding to the color circle.
				// Code uses this later to determine if a specified
				// point is within the region, using the IsVisible 
				// method.
				path.AddEllipse(_colorRectangle);

				_colorRegion = new Region(path);

				// Create the bitmap that contains the circular gradient.
				CreateGradient(backColor);
			} 
		}


		public Image ColorImage
		{
			get{ return _colorImage;}
		}


		public Region ColorRegion
		{
			get{ return _colorRegion;}
		}


//		public PointF Center
//		{
//			get{ return _centerPoint;}
//		}
//
//
//		public float Radius
//		{
//			get{ return _radius;}
//		}


		public RectangleF ColorRectangle
		{
			get{ return _colorRectangle;}
		}


		public HSB HSB
		{
			get{ return _hsb;}
			set
			{
				if( _hsb.Equals( value)) return;
				
				_hsb = value;
				_colorPoint = CalculateWheelPoint( _hsb );
			}

		}


		public PointF ColorPoint
		{
			get{ return _colorPoint;}
			set
			{
				if( _colorPoint == value ) return;
				
				_hsb = CalculateWheelColor( value );//Also calculates the point
			}

		}


		private void CreateGradient( Color backColor ) 
		{
			// Create a new PathGradientBrush, supplying
			// an array of points created by calling
			// the GetPoints method.
			using (PathGradientBrush pgb = new PathGradientBrush(GetPoints(_radius, new PointF(_radius, _radius))))
			{
				// Set the various properties. Note the SurroundColors
				// property, which contains an array of points, 
				// in a one-to-one relationship with the points
				// that created the gradient.
				pgb.CenterColor = Color.White;
				pgb.CenterPoint = new PointF(_radius,_radius);
				pgb.SurroundColors = GetColors();

				// Create a new bitmap containing
				// the color wheel gradient, so the 
				// code only needs to do all this 
				// work once. Later code uses the bitmap
				// rather than recreating the gradient.
				//Size s = Size.Ceiling( _colorRectangle.Size );
                Size s = Size.Truncate(_colorRectangle.Size);

				_colorImage = new Bitmap(s.Width, s.Height, PixelFormat.Format32bppArgb);
				using (Graphics newGraphics = Graphics.FromImage(_colorImage))
				{
					Rectangle rect = new Rectangle( 0, 0, s.Width, s.Height);
					newGraphics.FillEllipse(pgb, rect);
                    newGraphics.SmoothingMode = SmoothingMode.AntiAlias;
					using( Pen pen = new Pen( backColor, 2f) )
					newGraphics.DrawEllipse( pen, rect);
				}
			} 
		}


		private static PointF[] GetPoints(float radius,  PointF centerPoint) 
		{
			// Generate the array of points that describe
			// the locations of the COLOR_COUNT colors to be 
			// displayed on the color wheel.
			PointF[] Points = new PointF[COLOR_COUNT];

			for ( int i = 0 ; i <= COLOR_COUNT - 1; i++ )
				Points[i] = GetPoint((i * 360f) / (float)COLOR_COUNT, radius, centerPoint);
			
			return Points;
		}


		private static PointF GetPoint(float degrees, float radius, PointF centerPoint) 
		{
			// Given the center of a circle and its _radius, along
			// with the angle corresponding to the point, find the coordinates. 
			// In other words, conver  t from polar to rectangular coordinates.
			double radians = degrees / DEGREES_PER_RADIAN;

			return new PointF(
				(float)(centerPoint.X + (radius * (float)Math.Cos(radians))), 
				(float)(centerPoint.Y - (radius * (float)Math.Sin(radians))));
		}


		private static Color[] GetColors() 
		{
			// Create an array of COLOR_COUNT
			// colors, looping through all the 
			// hues between 0 and 255, broken
			// into COLOR_COUNT intervals. HSV is
			// particularly well-suited for this, 
			// because the only value that changes
			// as you create colors is the Hue.
			Color[] Colors = new Color[COLOR_COUNT];

			for ( int i = 0 ; i <= COLOR_COUNT - 1; i++)
			{
				HSB hsb = new HSB(((int)((i * 360f) / (float)COLOR_COUNT))%360, 100, 100);	//100%
				Colors[i] = ColorConverter.HsbToColor(hsb);//100%
			}

			return Colors;
		}


		private static float CalcDegrees(PointF pt) 
		{
			float degrees;

			if ( (int)pt.X == 0 ) 
			{
				// The point is on the y-axis. Determine whether 
				// it's above or below the x-axis, and return the 
				// corresponding angle. Note that the orientation of the
				// y-coordinate is backwards. That is, A positive Y value 
				// indicates a point BELOW the x-axis.
				if ( (int)pt.Y > 0 ) 
				{
					degrees = 270;
				} 
				else 
				{
					degrees = 90;
				}
			} 
			else 
			{
				// This value needs to be multiplied
				// by -1 because the y-coordinate
				// is opposite from the normal direction here.
				// That is, a y-coordinate that's "higher" on 
				// the form has a lower y-value, in this coordinate
				// system. So everything's off by a factor of -1 when
				// performing the ratio calculations.
				degrees = (float)(-Math.Atan((double)pt.Y / pt.X) * DEGREES_PER_RADIAN);

				// If the x-coordinate of the selected point
				// is to the left of the center of the circle, you 
				// need to add 180 degrees to the angle. ArcTan only
				// gives you a value on the right-hand side of the circle.
				if ( (int)pt.X < 0 ) 
				{
					degrees += 180;
				}

				// Ensure that the return value is 
				// between 0 and 360.
				degrees = (degrees + 360f) % 360f;
			}
			return degrees;
		}


		private HSB CalculateWheelColor(PointF mousePoint) //Was the draw method
		{
			// You've moved the mouse. 
			// Now update the screen to match.
			_colorPoint = mousePoint;

			// Calculate x and y distance from the center,
			// and then calculate the angle corresponding to the
			// new location.
			PointF delta = new PointF( mousePoint.X - _centerPoint.X, mousePoint.Y - _centerPoint.Y);
			float degrees = CalcDegrees(delta);

			// Calculate distance from the center to the new point 
			// as a fraction of the _radius. Use your old friend, 
			// the Pythagorean theorem, to calculate this value.
			double distance = Math.Sqrt(delta.X * delta.X + delta.Y * delta.Y) / _radius;

			if ( distance > 1 ) 
			{
				// Mouse is down, and outside the circle, but you 
				// were previously dragging in the color circle. 
				// What to do?
				// In that case, move the point to the edge of the 
				// circle at the correct angle.
				distance = 1;
				_colorPoint = GetPoint(degrees, _radius, _centerPoint);
			}

			return new HSB( (int)System.Math.Round( degrees), (int)System.Math.Round(distance*100.0), ColorRenderingValue.Brightness);

		}


		private PointF CalculateWheelPoint( HSB hsb )
		{
			// Convert color to real-world coordinates and then calculate
			// the various points. HSV.Hue represents the degrees (0 to 360), 
			// HSV.Saturation represents the _radius. 

			// Given the angle (HSB.Hue), and distance from 
			// the center (HSB.Saturation), and the center, 
			// calculate the point corresponding to 
			// the selected color, on the color wheel.
			return GetPoint(hsb.Hue, ((float)hsb.Saturation/100f) * _radius, _centerPoint);

//			return GetPoint((double)hue / 255 * 360, 
//				(double)saturation / 255 * _radius, 
//				_centerPoint);

		}




        #region IDisposable Members

        public void Dispose()
        {
            if (_colorRegion != null)
            {
                _colorRegion.Dispose();
                _colorRegion = null;
            }

            if (_colorImage != null)
            {
                _colorImage.Dispose();
                _colorImage = null;
            }
        }

        #endregion
    }
}
